/*
 *  WRenderer.cpp
 *  GLRender
 *
 *  Created by Patrick zulian on 5/5/10.
 *  Copyright 2010 University  of Lugano. All rights reserved.
 *
 */

#include "WRenderer.h"
#include "CCanvas.h"
#include "Configuration.h"


WRenderer::WRenderer()
: defaultAsteroidMat(RGBd(0.2, 1.0, 1.0)), defaultObjectiveMat(RGBd(0.2, 1.0, 0.2))
{
	
}

WRenderer::~WRenderer()
{
	utils::FreeValues< std::vector< Texture * > >(asteroidTextures);
	utils::FreeValues< std::vector< Texture * > >(planetTextures);
	
	std::vector<GLuint>::iterator it;
	
	for( it = asteroidMeshes.begin(); it != asteroidMeshes.end(); ++it )
		glDeleteLists((*it), 1);
	

	glDeleteLists(CROSS_DL,1);
	glDeleteLists(SHUTTLE_DL,1);
	glDeleteLists(SKYBOX_DL,1);
	glDeleteLists(CUBE_DL,1);
	glDeleteLists(TORUS_DL,1);
	glDeleteLists(SPHERE_DL,1);
	glDeleteLists(GLU_SPHERE_DL,1);
	glDeleteLists(FRAGMENT_DL,1);
}

void
WRenderer::initialize()
{
	asteroidMeshes.push_back(initMesh("../../obj/asteroid1.obj"));
	asteroidMeshes.push_back(initMesh("../../obj/asteroid2.obj"));
	asteroidMeshes.push_back(initMesh("../../obj/asteroid3.obj"));
	asteroidMeshes.push_back(initMesh("../../obj/asteroid4.obj"));
	asteroidMeshes.push_back(initMesh("../../obj/asteroid5.obj"));
	
	
	SKYBOX_DL = initMesh("../../obj/skybox.obj");
	SHUTTLE_DL = initMesh("../../obj/shuttle.obj");
	CUBE_DL = initCube();
	TORUS_DL = initTorus();
	CROSS_DL = initCross();
	SPHERE_DL = initMesh("../../obj/sphere.obj");
	GLU_SPHERE_DL = initGLUSphere(4);
	FRAGMENT_DL = initMesh("../../obj/fragment.obj");
	
	
	
	skyboxTexture.bind("../../obj/skybox.jpg");
	
	asteroidTextures.push_back(new Texture("../../obj/asteroid_tex1.jpg"));
	asteroidTextures.push_back(new Texture("../../obj/asteroid_tex2.jpg"));
	asteroidTextures.push_back(new Texture("../../obj/asteroid_tex3.jpg"));
	asteroidTextures.push_back(new Texture("../../obj/asteroid_tex4.jpg"));
	asteroidTextures.push_back(new Texture("../../obj/asteroid_tex5.jpg"));
	
	planetTextures.push_back(new Texture("../../obj/planet1.jpeg"));
	planetTextures.push_back(new Texture("../../obj/planet2.jpeg"));
	planetTextures.push_back(new Texture("../../obj/planet3.jpeg"));
	planetTextures.push_back(new Texture("../../obj/planet4.jpeg"));
	planetTextures.push_back(new Texture("../../obj/planet5.jpeg"));
	planetTextures.push_back(new Texture("../../obj/planet6.jpeg"));
	planetTextures.push_back(new Texture("../../obj/jupiter.jpeg"));


	
	cockpitRenderer.initialize();

	std::cout<< glGetError() <<std::endl;
	WMaterial mat(RGBd(1.0, 0.3, 0.2));
	mat.specular = RGBd(1.0, 0.3, 0.2);
	mat.ambient =  RGBd(1.0, 0.3, 0.2);
	spaceShipLight = Light::newLight(mat, Point3d(0,0,0));
}


void 
WRenderer::setUpAsteroid(GLObject * object)
{
	int objNum = ((float)rand()/RAND_MAX)*asteroidMeshes.size();
	int texNum = ((float)rand()/RAND_MAX)*asteroidTextures.size();
	object->setDLname(asteroidMeshes[objNum]);
	object->setTexture(asteroidTextures[texNum]);
}


void 
WRenderer::setUpPlanet(GLObject * object)
{
	int texNum = ((float)rand()/RAND_MAX)*planetTextures.size();
	object->setDLname(SPHERE_DL);
	object->setTexture(planetTextures[texNum]);
}

void
WRenderer::drawSphere(){
	glCallList(SPHERE_DL);
}

void 
WRenderer::drawTorus()
{
	
	if (Configuration::getInstance()->isDebugMode()) 
		glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
	
	glCallList(TORUS_DL);
	
	if (Configuration::getInstance()->isDebugMode()) 
		glPolygonMode( GL_FRONT_AND_BACK, GL_FILL);
}

void
WRenderer::renderCockpit(Player * player){
	spaceShipLight->setPosition(player->position());
	Point3d dir = player->velocity();
	dir.normalize();
	spaceShipLight->setParam(GL_SPOT_DIRECTION, dir);
	spaceShipLight->setParam(GL_SPOT_CUTOFF, Point3d(20,0,0));
	spaceShipLight->set();
	cockpitRenderer.renderPlayer(player);
}

void
WRenderer::drawLineCube()
{
	glCallList(CUBE_DL);
}
	

void
WRenderer::renderProjectile (const Projectile *p)
{
	glDisable(GL_LIGHTING);
	
	const Point3d pos = p->position();
	double angle = p->getAngle();
	
	glPushMatrix();
	glTranslated(pos.x(), pos.y(), pos.z());
	glPushMatrix();
	glScaled(p->getRadius(), p->getRadius(), p->getRadius());
	glPushMatrix();
	glRotated(angle, 1, 0, 0);
	double s = p->getStrength();
	double max = Projectile::INITIAL_STRENGTH*6;
	double mper = s/max;
	double per = 1.0-mper;
	if (mper > 1.0) {
		mper = 1.0;
		per = 0.0;
	}
	glColor3f(1.0*mper,0.0,1.0*per);
	drawTorus();
	glPopMatrix();
	glRotated(angle+90,0,1,0);
	glColor3f(1.0*mper,0.0,1.0*per);
	drawTorus();
	glPopMatrix();
	glScaled(0.5,0.5,0.5);
	glEnable(GL_BLEND);
	glColor4f(0.95,0.95,0.95,mper);
	glCallList(GLU_SPHERE_DL);
	glPopMatrix();
	glDisable(GL_BLEND);
	glEnable(GL_LIGHTING);
}

void
WRenderer::renderBonus (const Bonus *b)
{
	glDisable(GL_LIGHTING);
	glPushMatrix();
	const Point3d pos = b->position();
	const RGBd col = b->getColor();
	glTranslated(pos.x(), pos.y(), pos.z());
	glScaled(b->getRadius(),b->getRadius(),b->getRadius());
	glColor3f(col.r(),col.g(),col.b());
	drawSphere();
	glPopMatrix();
	glEnable(GL_LIGHTING);
}

void 
WRenderer::renderWNode(const WNode * node)
{
	glMatrixMode(GL_MODELVIEW);
	Point3d center = (node->getMin() + node->getMax());
	center /= 2.0;
	Point3d distance = node->getMax() - node->getMin();
	distance /= 2.0;
	
	glColor3f(1.0f, 1.0f, 1.0f/((float)node->level + 1.0f));
	glLineWidth(1);
	
	glPushMatrix();
	
	glTranslated(center[0], center[1], center[2]);
	glScaled(fabs(distance[0]), fabs(distance[1]), fabs(distance[2]));
	drawLineCube();

	glPopMatrix();


	return;
}

void
WRenderer::renderFragment()
{
	glCallList(FRAGMENT_DL);
}

void
WRenderer::renderAsteroid(const Asteroid * asteroid){
	glPushMatrix();
	if (asteroid->hasMaterial()) {
		asteroid->getMaterial()->set();
	} else {
		defaultAsteroidMat.set(); 
	}

	Texture * tex = asteroid->getTexture();
	if(!asteroid->hasTexture()) {
		tex = asteroidTextures[2];
	}
		
	
	if (Configuration::getInstance()->isDebugMode()) {
		glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
	} else {
		tex->enable();
		tex->bind();
	}
	
	defaultTransform(asteroid);
	if(asteroid->hasDL()) {
		asteroid->callDL();
	} else {
		glCallList(asteroidMeshes[0]);
	}
	
	if (Configuration::getInstance()->isDebugMode()) {
		glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
	} else
		tex->disable();
	glPopMatrix();
}


void
WRenderer::renderSkyBox(){
	glPushMatrix();

	glDisable( GL_LIGHTING );
	
	glColor3f(1.0f,1.0f,1.0f);
	
	skyboxTexture.enable();
	
	static const double scale = 2.0;
	glScaled(scale, scale, scale);
	skyboxTexture.bind();
	
	glCallList(SKYBOX_DL);
	
	skyboxTexture.disable();
	
	glPopMatrix();
	

	glEnable( GL_LIGHTING );
	
}


void
WRenderer::renderObjective(const WTarget * objective){
	glPushMatrix();
	
	defaultObjectiveMat.set();
	defaultTransform(objective);
	glCallList(SHUTTLE_DL);
	
	glPopMatrix();
}

void
WRenderer::renderCross()
{
	glCallList(CROSS_DL);
}

void
WRenderer::defaultTransform(const WObject * object)
{
	glTranslated(object->position().x(), object->position().y(), object->position().z());
	glScaled(object->getRadius()/1.1, object->getRadius()/1.1, object->getRadius()/1.1);

}

void
WRenderer::render(const GLObject * glObject)
{
	if (Configuration::getInstance()->isDebugMode()) 
		glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
	
	if (glObject->hasMaterial()) {
		glObject->getMaterial()->set();
	}
	
	Texture * tex = glObject->getTexture();
	if (glObject->hasTexture()) {
		tex->enable();
		tex->bind();
	}
	
	if (glObject->hasDL()) {
		glObject->callDL();
	} else {
		assert(false);
	}

	
	if (glObject->hasTexture()) {
		tex->disable();
	}	
	
	if (Configuration::getInstance()->isDebugMode()) 
		glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}


GLuint 
WRenderer::initMesh(const char * name)
{
	GLuint meshDL = glGenLists(1);
	
	glNewList(meshDL, GL_COMPILE);
	
	AbstractRenderer::renderMesh(name);
	
	glEndList();
	
	if(!glIsList(meshDL))
	{
		printf("NO LIST FOR %s \n", name);
	}
	
	return meshDL;
}


GLuint 
WRenderer::initCube()
{
	GLuint cube = glGenLists(1);
	
	glNewList(cube, GL_COMPILE);
	
	glBegin(GL_LINES);
	static const Point3d points[8] = { Point3d(-1,-1,-1), Point3d(-1,-1, 1), Point3d(-1,1,-1), Point3d(-1,1,1), Point3d(1,-1,-1), Point3d(1,-1,1), Point3d(1,1,-1), Point3d(1,1,1) };
	for (int i = 0; i < 8; ++i ) {
		for (int j = i+1; j < 8; ++j ) {
			if ((points[i] - points[j]).norm() > 2) {
				continue;
			}
			glVertex3d(points[i][0], points[i][1], points[i][2]);
			glVertex3d(points[j][0], points[j][1], points[j][2]);
		}
	}
	glEnd();
	
	
	glEndList();
	
	if(!glIsList(cube))
	{
		printf("NO LIST FOR cube\n");
	}
	
	return cube;
	
}


GLuint 
WRenderer::initCross()
{
	GLuint targetDL = glGenLists(1);
	
	glNewList(targetDL, GL_COMPILE);
	
	glBegin(GL_LINES);
	
	const double extent = 0.92, displacement = 0.08;
	glNormal3d(0, 0, 1);
	glVertex3d(0.0, displacement, 0.0);
	glVertex3d(0.0, displacement + extent, 0.0);
	
	glVertex3d(0.0, -displacement, 0.0);
	glVertex3d(0.0, -(displacement + extent), 0.0);
	
	glVertex3d(displacement, 0.0, 0.0);
	glVertex3d(displacement + extent, 0.0, 0.0);
	
	glVertex3d(-displacement, 0.0, 0.0);
	glVertex3d(-(displacement + extent), 0.0, 0.0);
	glEnd();
	
	glEndList();
	
	if(!glIsList(targetDL))
	{
		printf("NO LIST FOR Target\n");
	}
	
	return targetDL;
}


GLuint
WRenderer::initTorus()
{
	
	
	GLuint torus = glGenLists(1);
	
	glNewList(torus, GL_COMPILE);
	
	const int numc = 4, numt = 12;
	int i, j, k;
	double s, t, x, y, z, twopi;
	
	twopi = 2 * (double)M_PI;
	for (i = 0; i < numc; i++) {
		glBegin(GL_QUAD_STRIP);
		for (j = 0; j <= numt; j++) {
			for (k = 1; k >= 0; k--) {
				s = (i + k) % numc + 0.5;
				t = j % numt;
				
				x = (1+.1*cos(s*twopi/numc))*cos(t*twopi/numt);
				y = (1+.1*cos(s*twopi/numc))*sin(t*twopi/numt);
				z = .1 * sin(s * twopi / numc);
				glVertex3f(x, y, z);
			}
		}
		glEnd();
	}
	
	
	glEndList();
	
	if(!glIsList(torus))
	{
		printf("NO LIST FOR torus\n");
	}
	
	return torus;
	
}

GLuint 
WRenderer::initGLUSphere(const int n) {
	GLuint sphereDL = glGenLists(1);
	
	glNewList(sphereDL, GL_COMPILE);
	
	GLUquadricObj *sphere;
	sphere = gluNewQuadric();
	gluQuadricDrawStyle( sphere, GLU_FILL );
	gluSphere( sphere, 1.0, n, n );

	glEndList();
	
	if(!glIsList(sphereDL))
	{
		printf("NO LIST FOR torus\n");
	}
	
	return sphereDL;
}

void 
WRenderer::renderFlash(const RGBd &rgb, const int duration) 
{
	cockpitRenderer.startFlash(rgb, duration);
	
}
